﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Diagnostics;

namespace Citrix.XaAdminMobile.Util
{
    /// <summary>
    /// Writes trace to a logfile with timestamps and thread IDs before each message. Note: only
    /// messages supplied using WriteLine get the timestamp/thread ID prefix. Messages trace
    /// using Write do not get the prefix.
    /// </summary>
    /// <remarks>
    /// This is essentially a ripoff of TextWriterTraceListener with the extra functionality
    /// added in.
    /// </remarks>
    internal class TimestampTextFileTraceListener : TraceListener
    {
        /// <summary>
        /// The name of the file to write to.
        /// </summary>
        private string userSuppliedFileName;

        /// <summary>
        /// The text writer used to write to the file.
        /// </summary>
        private TextWriter writer;

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="fileName">The name of the file to write to.</param>
        public TimestampTextFileTraceListener(string fileName)
        {
            this.userSuppliedFileName = fileName;
        }

        /// <summary>
        /// Dispose handler.
        /// </summary>
        /// <param name="disposing"></param>
        protected override void Dispose(bool disposing)
        {
            try
            {
                this.Close();
            }
            finally
            {
                base.Dispose(disposing);
            }
        }

        /// <summary>
        /// Closes the contained text writer.
        /// </summary>
        public override void Close()
        {
            if (this.writer != null)
            {
                try
                {
                    this.writer.Close();
                }
                catch (ObjectDisposedException)
                {
                }
            }
            this.writer = null;
        }

        /// <summary>
        /// Flushes all trace content to the trace file.
        /// </summary>
        public override void Flush()
        {
            if (this.EnsureWriter())
            {
                try
                {
                    this.writer.Flush();
                }
                catch (ObjectDisposedException)
                {
                }
            }
        }

        /// <summary>
        /// Writes the supplied message to the text file.
        /// </summary>
        /// <param name="message">The message to trace.</param>
        public override void Write(string message)
        {
            if (this.EnsureWriter())
            {
                if (base.NeedIndent)
                {
                    this.WriteIndent();
                }
                try
                {
                    this.writer.Write(message);
                }
                catch (ObjectDisposedException)
                {
                }
            }
        }

        /// <summary>
        /// Writes a line of trace with a timestamp pre-pended to the message.
        /// </summary>
        /// <param name="message">The message to trace.</param>
        public override void WriteLine(string message)
        {
            if (this.EnsureWriter())
            {
                if (base.NeedIndent)
                {
                    this.WriteIndent();
                }
                try
                {
                    this.writer.WriteLine(string.Format("{0}:[{1:D3}]: {2}",
                        DateTime.UtcNow.ToString("HH:mm:ss:fff"),
                        System.Threading.Thread.CurrentThread.ManagedThreadId,
                        message));
                    base.NeedIndent = true;
                }
                catch (ObjectDisposedException)
                {
                }
            }
        }

        /// <summary>
        /// Ensures that the underlying text writer has been created.
        /// </summary>
        /// <returns></returns>
        private bool EnsureWriter()
        {
            bool flag = true;
            if (this.writer == null)
            {
                flag = false;
                if (this.userSuppliedFileName == null)
                {
                    return flag;
                }
                Encoding encodingWithFallback = GetEncodingWithFallback(new UTF8Encoding(false));
                string fullPath = Path.GetFullPath(this.userSuppliedFileName);
                string directoryName = Path.GetDirectoryName(fullPath);
                string fileName = Path.GetFileName(fullPath);

                // Try twice - if we can't write out to the supplied file, we try a different
                // file in the same location but with a randomly generated name.
                for (int i = 0; i < 2; i++)
                {
                    try
                    {
                        this.writer = new StreamWriter(fullPath, true, encodingWithFallback, 0x1000);
                        flag = true;
                        break;
                    }
                    catch (IOException)
                    {
                        fileName = Guid.NewGuid().ToString() + fileName;
                        fullPath = Path.Combine(directoryName, fileName);
                    }
                    catch (UnauthorizedAccessException)
                    {
                        break;
                    }
                    catch (Exception)
                    {
                        break;
                    }
                }
                if (!flag)
                {
                    this.userSuppliedFileName = null;
                }
            }
            return flag;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="encoding"></param>
        /// <returns></returns>
        private static Encoding GetEncodingWithFallback(Encoding encoding)
        {
            Encoding encodingClone = (Encoding)encoding.Clone();
            encodingClone.EncoderFallback = EncoderFallback.ReplacementFallback;
            encodingClone.DecoderFallback = DecoderFallback.ReplacementFallback;
            return encodingClone;
        }
    }
}
